package cn.tedu.mall.seckill.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.restful.JsonPage;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.pojo.product.vo.SpuDetailStandardVO;
import cn.tedu.mall.pojo.product.vo.SpuStandardVO;
import cn.tedu.mall.pojo.seckill.model.SeckillSpu;
import cn.tedu.mall.pojo.seckill.vo.SeckillSpuDetailSimpleVO;
import cn.tedu.mall.pojo.seckill.vo.SeckillSpuVO;
import cn.tedu.mall.product.service.seckill.IForSeckillSpuService;
import cn.tedu.mall.seckill.mapper.SeckillSpuMapper;
import cn.tedu.mall.seckill.service.ISeckillSpuService;
import cn.tedu.mall.seckill.utils.RedisBloomUtils;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class SeckillSpuServiceImpl implements ISeckillSpuService {
    @Autowired
    private SeckillSpuMapper seckillSpuMapper;

    @DubboReference
    private IForSeckillSpuService dubboSeckillSpuService;

    //定义一个spuDetail对应的key
    public static final String SECKILL_SPU_DETAIL_VO_PREFIX = "seckill:spu:detail:vo:";
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisBloomUtils redisBloomUtils;

    //分页查询秒杀商品列表
    @Override
    public JsonPage<SeckillSpuVO> listSeckillSpus(Integer page, Integer pageSize) {
        //设置分页条件
        PageHelper.startPage(page,pageSize);
        //执行查询操作
        List<SeckillSpu> seckillSpus = seckillSpuMapper.selectSeckillSpu();
        //声明一个返回值类型
        List<SeckillSpuVO> seckillSpuVOS = new ArrayList<>();
        //遍历seckillSpus，转成seckillSpuVOS
        for (SeckillSpu seckillSpu : seckillSpus) {
            Long spuId = seckillSpu.getSpuId();
            //跟怒spuId获取spu详情
            SpuStandardVO spuStandardVO = dubboSeckillSpuService.getSpuById(spuId);
            //声明SeckillSpuVO类型对象
            SeckillSpuVO seckillSpuVO = new SeckillSpuVO();
            BeanUtils.copyProperties(spuStandardVO,seckillSpuVO);
            //赋值秒杀信息
            seckillSpuVO.setSeckillListPrice(seckillSpu.getListPrice());
            seckillSpuVO.setStartTime(seckillSpu.getStartTime());
            seckillSpuVO.setEndTime(seckillSpu.getEndTime());
            seckillSpuVOS.add(seckillSpuVO);
        }
        //千万要记得返回
        return JsonPage.restPage(new PageInfo<>(seckillSpuVOS));
    }

    @Override
    public SeckillSpuVO getSeckillSpu(Long spuId) {
        //先判断spuId是否在布隆过滤器中存在
        //获取对应的key
        String bloomFilterKey = SeckillCacheUtils.getBloomFilterKey(LocalDate.now());
        log.info("当前批次的布隆过滤器的key为{}",bloomFilterKey);
        //如果在布隆过滤器中不存在，就直接终止程序
        if(!redisBloomUtils.bfexists(bloomFilterKey,spuId+"")){
            throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"您访问的商品不存在");
        }
        //程序到此，布隆过滤器判断存在,但是不一定真实存在，所以还要加一个判断
        //声明一个返回值类型
        SeckillSpuVO seckillSpuVO = null;
        //查询缓存，先获取key
        String seckillSpuVOKey = SeckillCacheUtils.getSeckillSpuVOKey(spuId);
        //判断这个key是否存在
        if(redisTemplate.hasKey(seckillSpuVOKey)){
            seckillSpuVO = (SeckillSpuVO) redisTemplate.boundValueOps(seckillSpuVOKey).get();
        }else{
            //缓存中没有数据，查询数据库
            SeckillSpu seckillSpu = seckillSpuMapper.selectSeckillSpuBySpuId(spuId);
            //判断seckillSpu是否为null，因为布隆过滤器误判会进入这里
            if(seckillSpu==null){
                throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"您访问的商品不存在");
            }
            //如果不为null，目前我们有秒杀spu的信息，具体的信息需要远程调用product模块来获取
            SpuStandardVO spuStandardVO = dubboSeckillSpuService.getSpuById(spuId);
            //将spuStandardVO转成seckillSpuVO
            seckillSpuVO = new SeckillSpuVO();
            BeanUtils.copyProperties(spuStandardVO,seckillSpuVO);
            //将秒杀信息赋值到seckillSpuVO中
            seckillSpuVO.setSeckillListPrice(seckillSpu.getListPrice()); //秒杀价格
            seckillSpuVO.setStartTime(seckillSpu.getStartTime()); //开始时间
            seckillSpuVO.setEndTime(seckillSpu.getEndTime()); //结束时间
            //将seckillSpuVO保存到Redis中
            redisTemplate.boundValueOps(seckillSpuVOKey).set(seckillSpuVO,
                    24*60*60+RandomUtils.nextInt(100),TimeUnit.SECONDS);
        }
         //判断当前时间是否在秒杀时间范围内
         //不再查询数据库，直接从seckillSpuVO对象中获取开始时间和结束时间
        LocalDateTime startTime = seckillSpuVO.getStartTime();
        LocalDateTime endTime = seckillSpuVO.getEndTime();
        LocalDateTime nowTime = LocalDateTime.now();
        //我们判断nowTime是否大于startTime并且小于endTime
        //使用时间对象Duration，来进行判断
        //如果为负值，返回信息是一个negative
        Duration afterTime = Duration.between(nowTime, startTime);
        Duration beforeTime = Duration.between(endTime, nowTime);
        //如果afterTime和beforeTime返回信息都是negative，说明当前时间在秒杀时间范围内
        if(afterTime.isNegative()&&beforeTime.isNegative()){
            //可以进行购买
            //购买路径是根据url属性，设计的购买路径为"/seckill/{randCode}"
            //获取随机码
            String randCodeKey = SeckillCacheUtils.getRandCodeKey(spuId);
            int randCode = (int) redisTemplate.boundValueOps(randCodeKey).get();
            //复制到Url属性中
            seckillSpuVO.setUrl("/seckill/"+randCode);
        }
        //seckillSpuVO对象中有url属性，返回到前端，由前端保存，之后前端利用这个url属性才能发起购买请求
        return seckillSpuVO;
    }

    @Override
    public SeckillSpuDetailSimpleVO getSeckillSpuDetail(Long spuId) {
        //获取对应的key
        String seckillDetailKey = SECKILL_SPU_DETAIL_VO_PREFIX + spuId;
        //声明返回值类型
        SeckillSpuDetailSimpleVO seckillSpuDetailSimpleVO = null;
        //判断缓存中是否存在
        if(redisTemplate.hasKey(seckillDetailKey)){
            seckillSpuDetailSimpleVO =
                    (SeckillSpuDetailSimpleVO) redisTemplate.boundValueOps(seckillDetailKey).get();
        }else{
            SpuDetailStandardVO spuDetailStandardVO = dubboSeckillSpuService.getSpuDetailById(spuId);
            seckillSpuDetailSimpleVO = new SeckillSpuDetailSimpleVO();
            BeanUtils.copyProperties(spuDetailStandardVO,seckillSpuDetailSimpleVO);
            //存储到redis中
            redisTemplate.boundValueOps(seckillDetailKey).set(seckillSpuDetailSimpleVO,
                    24*60*60+ RandomUtils.nextInt(100), TimeUnit.SECONDS);
        }
        return seckillSpuDetailSimpleVO;
    }
}
